"""
Cross Site Request Forgery decorator.
"""

from binascii import b2a_hex
import logging

from bodhi.wsgi import HttpError
from bodhi.utils.http import patch_vary_headers

try:
    from os import urandom
    urandom(16)
    def new_token():
        return b2a_hex(urandom(16)).decode('latin-1')
except:
    # calculating uuid4 need more than 40 micro seconds, otherwise, urandom: 15 micro seconds
    from uuid import uuid4 
    def new_token():
        return b2a_hex(uuid4().bytes).decode('latin-1')

# also used as csrf field name
csrf_cookie_name = 'csrf'

def csrf_tag(view):
    request = view.request
    token = getattr(request, '_csrf_token', None)
    if not token:
        token = request.get_cookie(csrf_cookie_name)
        if token:
            # In case of cookies from untrusted sources, we strip anything
            # dangerous at this point, so that the cookie + token will have the
            # same, sanitized value.
            token = _sanitize_token(token)
        else:
            token = new_token()
        request._csrf_token = token
    return '<input type="hidden" name="{0}" value="{1}"/>'.format(csrf_cookie_name, token)

import re
re_token = re.compile('[^a-zA-Z0-9]')

def _sanitize_token(token):
    # Allow only alphanum, and ensure we return a 'str' for the sake of the post
    # processing middleware.
    token = re_token.sub('', token)
    if token == "":
        # In case the cookie has been truncated to nothing at some point.
        return _get_new_csrf_key()
    else:
        return token

class CsrfFilter:
    order = 20

    def __call__(self, view, next):
        request = view.request
        if request.method == 'POST':
            request_csrf_token = request.arguments.get(csrf_cookie_name)
            if not request_csrf_token:
                request_csrf_token = request.environ.get('HTTP-X-CSRFTOKEN')
            if not request_csrf_token:
                self.reject(request, reason="CSRF token missing or incorrect.")
            if request_csrf_token != request.get_cookie(csrf_cookie_name):
                self.reject(request, reason="CSRF cookie does not match POST argument")

            if request.is_secure:
                # Strict referer checking for HTTPS
                referer = request.environ.get('HTTP_REFERER')
                if referer is None:
                    self.reject(request, reason="Referer checking failed - no Referer.")

                # The following check ensures that the referer is HTTPS,
                # the domains match and the ports match.  This might be too strict.
                good_referer = 'https://' + request.host
                if not (referer.startswith(good_referer) and referer[len(good_referer):len(good_referer) + 1] in ('/', '', '?')):
                    self.reject(request, reason="Referer checking failed - {0} does not match {1}.".format(referer, good_referer))

        next()

        token = getattr(request, '_csrf_token', None)
        if token:  # only csrf_tag set attribute _csrf_token on request
            # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
            view.response.set_cookie(csrf_cookie_name, token, max_age=90*86400)#, httponly=True)
            patch_vary_headers(view.response, ('Cookie',))

    def reject(self, request, reason):
        logging.warning('Forbidden (%s): %s - %s',
                reason, 
                request.path,
                request.remote_ip)
        raise HttpError(403, reason)

